package com.mc.fastkit.view.shape

import android.content.Context
import android.content.res.ColorStateList
import android.content.res.TypedArray
import android.graphics.Outline
import android.graphics.Path
import android.graphics.Rect
import android.graphics.RectF
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import android.graphics.drawable.LayerDrawable
import android.graphics.drawable.RippleDrawable
import android.graphics.drawable.StateListDrawable
import android.os.Build
import android.util.AttributeSet
import android.view.View
import android.view.ViewOutlineProvider
import androidx.core.content.withStyledAttributes
import androidx.core.graphics.drawable.toDrawable
import androidx.core.graphics.toRectF
import com.mc.fastkit.R
import com.mc.fastkit.ext.getColor
import com.mc.fastkit.view.StateType
import kotlin.math.min

/**
 * 设置View形状的代理类，当attrs不为空时，可从XML中读取属性，反之需要通过方法手动设置
 * @author: MasterChan
 * @date: 2023-03-24 11:46:09
 */
class ShapedViewDelegate(
    context: Context? = null,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0,
    defStyleRes: Int = 0
) : IShapedView {

    private lateinit var target: View
    override var isRounded: Boolean = false
    override var useRipple: Boolean = true
    override var rippleColor: Int = 0
    override var rippleMask: Drawable? = null
    override var contentDrawable: Drawable? = null
    override var contentDrawableState: Drawable? = null
    override var contentDrawableDisable: Drawable? = null
    override var normalAttrs: GradientAttrs = NormalGradientAttrs(this)
    override var stateAttrs: GradientAttrs = GradientAttrs(this)
    override var disableAttrs: GradientAttrs = GradientAttrs(this)
    override var stateType: StateType = StateType.NONE
    override var renderMode: Int = RenderMode.BACKGROUND
    private var isSelected: Boolean? = null
    private var isDisable: Boolean? = null
    private val outlinePath = Path()

    init {
        if (context != null && attrs != null) {
            initAttrs(context, attrs, defStyleAttr, defStyleRes)
        }
    }

    private fun initAttrs(
        context: Context,
        attrs: AttributeSet,
        defStyleAttr: Int,
        defStyleRes: Int
    ) {
        context.withStyledAttributes(attrs, R.styleable.ShapedView, defStyleAttr, defStyleRes) {
            isRounded = getBoolean(R.styleable.ShapedView_mc_isRounded, false)
            useRipple = getBoolean(R.styleable.ShapedView_mc_useRipple, false)
            rippleColor = getColor(R.styleable.ShapedView_mc_rippleColor, "#33FFFFFF")
            rippleMask = getDrawable(R.styleable.ShapedView_mc_rippleMask)
            contentDrawable = getDrawable(R.styleable.ShapedView_mc_contentRes)
            contentDrawableState = getDrawable(R.styleable.ShapedView_mc_contentResState)
            contentDrawableDisable = getDrawable(R.styleable.ShapedView_mc_contentResDisable)
            if (hasValue(R.styleable.ShapedView_mc_selected)) {
                isSelected = getBoolean(R.styleable.ShapedView_mc_selected, false)
            }
            if (hasValue(R.styleable.ShapedView_mc_disable)) {
                isDisable = getBoolean(R.styleable.ShapedView_mc_disable, false)
            }
            stateType = StateType.from(getInt(R.styleable.ShapedView_mc_stateType, -1))
            renderMode = getInt(R.styleable.ShapedButton_mc_renderMode, RenderMode.BACKGROUND)

            initNormalAttrs(this)
            initStateAttrs(this)
            initDisableAttrs(this)
        }
    }

    private fun initNormalAttrs(a: TypedArray) = normalAttrs.apply {
        val radius = a.getDimension(R.styleable.ShapedView_mc_radius, 0f)
        setTopLeftRadius(a.getDimension(R.styleable.ShapedView_mc_topLeftRadius, radius))
        setBottomLeftRadius(a.getDimension(R.styleable.ShapedView_mc_bottomLeftRadius, radius))
        setTopRightRadius(a.getDimension(R.styleable.ShapedView_mc_topRightRadius, radius))
        setBottomRightRadius(a.getDimension(R.styleable.ShapedView_mc_bottomRightRadius, radius))
        setContentColor(a.getColor(R.styleable.ShapedView_mc_contentColor, 0))
        setContentMaskColor(a.getColor(R.styleable.ShapedView_mc_contentMaskColor, 0))
        setStrokeWidth(a.getDimensionPixelOffset(R.styleable.ShapedView_mc_strokeWidth, 0))
        setStrokeColor(a.getColor(R.styleable.ShapedView_mc_strokeColor, "#D6D6D6"))
        setDashWidth(a.getDimension(R.styleable.ShapedView_mc_dashWidth, 0f))
        setDashGap(a.getDimension(R.styleable.ShapedView_mc_dashGap, 0f))
        setGradientType(a.getInt(R.styleable.ShapedView_mc_gradientType, GradientType.LINEAR))
        setGradientOrientation(
            getGradientOrientation(a.getInt(R.styleable.ShapedView_mc_gradientOrientation, 0))
        )
        setGradientRadius(a.getDimension(R.styleable.ShapedView_mc_gradientRadius, 0f))
        setGradientCenterX(a.getFloat(R.styleable.ShapedView_mc_gradientCenterX, 0.5f))
        setGradientCenterY(a.getFloat(R.styleable.ShapedView_mc_gradientCenterY, 0.5f))
        setStartColor(a.getColor(R.styleable.ShapedView_mc_gradientStartColor, 0))
        setCenterColor(a.getColor(R.styleable.ShapedView_mc_gradientCenterColor, 0))
        setEndColor(a.getColor(R.styleable.ShapedView_mc_gradientEndColor, 0))
        setStartColorWeight(a.getInt(R.styleable.ShapedView_mc_gradientStartColorWeight, 1))
        setCenterColorWeight(a.getInt(R.styleable.ShapedView_mc_gradientCenterColorWeight, 1))
        setEndColorWeight(a.getInt(R.styleable.ShapedView_mc_gradientEndColorWeight, 1))
    }

    private fun initStateAttrs(a: TypedArray) = stateAttrs.apply {
        if (a.hasValue(R.styleable.ShapedView_mc_radius_state)) {
            val radius = a.getDimension(R.styleable.ShapedView_mc_radius_state, 0f)
            setTopLeftRadius(a.getDimension(R.styleable.ShapedView_mc_topLeftRadius_state, radius))
            setBottomLeftRadius(
                a.getDimension(R.styleable.ShapedView_mc_bottomLeftRadius_state, radius)
            )
            setTopRightRadius(
                a.getDimension(R.styleable.ShapedView_mc_topRightRadius_state, radius)
            )
            setBottomRightRadius(
                a.getDimension(R.styleable.ShapedView_mc_bottomRightRadius_state, radius)
            )
        } else {
            setTopLeftRadius(
                a.getDimension(R.styleable.ShapedView_mc_topLeftRadius_state, topLeftRadius)
            )
            setBottomLeftRadius(
                a.getDimension(R.styleable.ShapedView_mc_bottomLeftRadius_state, bottomLeftRadius)
            )
            setTopRightRadius(
                a.getDimension(R.styleable.ShapedView_mc_topRightRadius_state, topRightRadius)
            )
            setBottomRightRadius(
                a.getDimension(R.styleable.ShapedView_mc_bottomRightRadius_state, bottomRightRadius)
            )
        }
        setContentColor(a.getColor(R.styleable.ShapedView_mc_contentColor_state, contentColor))
        setStrokeWidth(
            a.getDimensionPixelOffset(R.styleable.ShapedView_mc_strokeWidth_state, strokeWidth)
        )
        setStrokeColor(a.getColor(R.styleable.ShapedView_mc_strokeColor_state, strokeColor))
        setDashWidth(a.getDimension(R.styleable.ShapedView_mc_dashWidth_state, dashWidth))
        setDashGap(a.getDimension(R.styleable.ShapedView_mc_dashGap_state, dashGap))
        setGradientType(a.getInt(R.styleable.ShapedView_mc_gradientType_state, gradientType))
        setGradientOrientation(
            getGradientOrientation(
                a.getInt(
                    R.styleable.ShapedView_mc_gradientOrientation_state, gradientOrientation.ordinal
                )
            )
        )
        setGradientRadius(
            a.getDimension(R.styleable.ShapedView_mc_gradientRadius_state, gradientRadius)
        )
        setGradientCenterX(
            a.getFloat(R.styleable.ShapedView_mc_gradientCenterX_state, gradientCenterX)
        )
        setGradientCenterY(
            a.getFloat(R.styleable.ShapedView_mc_gradientCenterY_state, gradientCenterY)
        )
        setStartColor(a.getColor(R.styleable.ShapedView_mc_gradientStartColor_state, startColor))
        setCenterColor(a.getColor(R.styleable.ShapedView_mc_gradientCenterColor_state, centerColor))
        setEndColor(a.getColor(R.styleable.ShapedView_mc_gradientEndColor_state, endColor))
        setStartColorWeight(a.getInt(R.styleable.ShapedView_mc_gradientStartColorWeight_state, 1))
        setCenterColorWeight(a.getInt(R.styleable.ShapedView_mc_gradientCenterColorWeight_state, 1))
        setEndColorWeight(a.getInt(R.styleable.ShapedView_mc_gradientEndColorWeight_state, 1))
    }

    private fun initDisableAttrs(a: TypedArray) = disableAttrs.apply {
        if (a.hasValue(R.styleable.ShapedView_mc_radius_disable)) {
            val radius = a.getDimension(R.styleable.ShapedView_mc_radius_disable, 0f)
            setTopLeftRadius(
                a.getDimension(R.styleable.ShapedView_mc_topLeftRadius_disable, radius)
            )
            setBottomLeftRadius(
                a.getDimension(R.styleable.ShapedView_mc_bottomLeftRadius_disable, radius)
            )
            setTopRightRadius(
                a.getDimension(R.styleable.ShapedView_mc_topRightRadius_disable, radius)
            )
            setBottomRightRadius(
                a.getDimension(R.styleable.ShapedView_mc_bottomRightRadius_disable, radius)
            )
        } else {
            setTopLeftRadius(
                a.getDimension(R.styleable.ShapedView_mc_topLeftRadius_disable, topLeftRadius)
            )
            setBottomLeftRadius(
                a.getDimension(R.styleable.ShapedView_mc_bottomLeftRadius_disable, bottomLeftRadius)
            )
            setTopRightRadius(
                a.getDimension(R.styleable.ShapedView_mc_topRightRadius_disable, topRightRadius)
            )
            setBottomRightRadius(
                a.getDimension(
                    R.styleable.ShapedView_mc_bottomRightRadius_disable, bottomRightRadius
                )
            )
        }
        setContentColor(a.getColor(R.styleable.ShapedView_mc_contentColor_disable, contentColor))
        setStrokeWidth(
            a.getDimensionPixelOffset(R.styleable.ShapedView_mc_strokeWidth_disable, strokeWidth)
        )
        setStrokeColor(a.getColor(R.styleable.ShapedView_mc_strokeColor_disable, strokeColor))
        setDashWidth(a.getDimension(R.styleable.ShapedView_mc_dashWidth_disable, dashWidth))
        setDashGap(a.getDimension(R.styleable.ShapedView_mc_dashGap_disable, dashGap))
        setGradientType(a.getInt(R.styleable.ShapedView_mc_gradientType_disable, gradientType))
        setGradientOrientation(
            getGradientOrientation(
                a.getInt(
                    R.styleable.ShapedView_mc_gradientOrientation_disable,
                    gradientOrientation.ordinal
                )
            )
        )
        setGradientRadius(
            a.getDimension(R.styleable.ShapedView_mc_gradientRadius_disable, gradientRadius)
        )
        setGradientCenterX(
            a.getFloat(R.styleable.ShapedView_mc_gradientCenterX_disable, gradientCenterX)
        )
        setGradientCenterY(
            a.getFloat(R.styleable.ShapedView_mc_gradientCenterY_disable, gradientCenterY)
        )
        setStartColor(a.getColor(R.styleable.ShapedView_mc_gradientStartColor_disable, startColor))
        setCenterColor(
            a.getColor(R.styleable.ShapedView_mc_gradientCenterColor_disable, centerColor)
        )
        setEndColor(a.getColor(R.styleable.ShapedView_mc_gradientEndColor_disable, endColor))
        setStartColorWeight(a.getInt(R.styleable.ShapedView_mc_gradientStartColorWeight_disable, 1))
        setCenterColorWeight(
            a.getInt(R.styleable.ShapedView_mc_gradientCenterColorWeight_disable, 1)
        )
        setEndColorWeight(a.getInt(R.styleable.ShapedView_mc_gradientEndColorWeight_disable, 1))
    }

    override fun into(target: View) {
        this.target = target
        if (renderMode == RenderMode.BACKGROUND) {
            this.target.background = createDrawable()
        } else {
            this.target.foreground = createDrawable()
        }
        if (isSelected != null) {
            this.target.isSelected = isSelected!!
            isSelected = null
        }
        if (isDisable != null) {
            this.target.isEnabled = !isDisable!!
            isDisable = null
        }
        setViewOutline()
    }

    override fun setup() = into(target)

    private fun setViewOutline() {
        target.outlineProvider = object : ViewOutlineProvider() {
            override fun getOutline(view: View, outline: Outline) {
                outlinePath.reset()
                when {
                    //圆形
                    isRounded -> {
                        val size = min(view.width, view.height)
                        val left = (view.width - size) / 2
                        val top = (view.height - size) / 2
                        val right = left + size
                        val bottom = top + size
                        outlinePath.addOval(
                            Rect(left, top, right, bottom).toRectF(), Path.Direction.CW
                        )
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
                            outline.setPath(outlinePath)
                        } else {
                            outline.setConvexPath(outlinePath)
                        }
                    }

                    //setPath在有些机型上不生效
                    getCurAttrs(view).topLeftRadius == getCurAttrs(view).topRightRadius &&
                            getCurAttrs(view).topLeftRadius == getCurAttrs(view).bottomLeftRadius &&
                            getCurAttrs(view).topLeftRadius == getCurAttrs(
                        view
                    ).bottomRightRadius -> {
                        outline.setRoundRect(
                            0, 0, view.width, view.height, getCurAttrs(view).topLeftRadius
                        )
                    }

                    else -> {
                        val attrs = getCurAttrs(view)
                        val rect = RectF(0f, 0f, view.width.toFloat(), view.height.toFloat())
                        if (attrs.isRoundedRect) {
                            getCurAttrs(view).apply {
                                outlinePath.addRoundRect(
                                    rect,
                                    floatArrayOf(
                                        topLeftRadius, topLeftRadius, topRightRadius,
                                        topRightRadius,
                                        bottomRightRadius, bottomRightRadius, bottomLeftRadius,
                                        bottomLeftRadius
                                    ),
                                    Path.Direction.CW
                                )
                            }
                        } else {
                            outlinePath.addRect(rect, Path.Direction.CW)
                        }
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
                            outline.setPath(outlinePath)
                        } else {
                            outline.setConvexPath(outlinePath)
                        }
                    }
                }
            }
        }
        target.setLayerType(View.LAYER_TYPE_HARDWARE, null)
        target.clipToOutline = true
    }

    private fun getCurAttrs(view: View): GradientAttrs {
        val drawable = if (renderMode == RenderMode.BACKGROUND) {
            view.background
        } else {
            view.foreground
        }
        return when {
            drawable.state.contains(android.R.attr.state_pressed) ||
                    drawable.state.contains(android.R.attr.state_selected) ||
                    drawable.state.contains(android.R.attr.state_checked) -> {
                stateAttrs
            }

            drawable.state.contains(-android.R.attr.state_enabled) -> {
                disableAttrs
            }

            else -> {
                normalAttrs
            }
        }
    }

    private fun createDrawable(): Drawable {
        val drawable = StateListDrawable()

        //Normal
        var normalDrawable = contentDrawable ?: GradientDrawable().apply {
            setGradientDrawable(this, normalAttrs)
        }
        if (useRipple) {
            normalDrawable = RippleDrawable(
                ColorStateList.valueOf(rippleColor), normalDrawable, rippleMask
            )
        }

        //State
        if (stateType != StateType.NONE) {
            val state = stateType.androidState
            drawable.addState(intArrayOf(android.R.attr.state_enabled, -state), normalDrawable)
            var stateDrawable = contentDrawableState ?: GradientDrawable().apply {
                setGradientDrawable(this, stateAttrs)
            }
            when {
                useRipple -> {
                    stateDrawable = RippleDrawable(
                        ColorStateList.valueOf(rippleColor), stateDrawable, rippleMask
                    )
                }

                normalAttrs.contentMaskColor != 0 -> {
                    stateDrawable = LayerDrawable(
                        arrayOf(normalDrawable, normalAttrs.contentMaskColor.toDrawable())
                    )
                }
            }
            drawable.addState(intArrayOf(android.R.attr.state_enabled, state), stateDrawable)
        } else {
            drawable.addState(intArrayOf(android.R.attr.state_enabled), normalDrawable)
        }

        //Disable
        val disableDrawable = contentDrawableDisable ?: GradientDrawable().apply {
            setGradientDrawable(this, disableAttrs)
        }
        drawable.addState(intArrayOf(-android.R.attr.state_enabled), disableDrawable)
        return drawable
    }

    private fun setGradientDrawable(drawable: GradientDrawable, attrs: GradientAttrs) {
        attrs.apply {
            val radii = if (isRounded) {
                val radius = min(target.width, target.height) / 2f
                floatArrayOf(radius, radius, radius, radius, radius, radius, radius, radius)
            } else {
                floatArrayOf(
                    topLeftRadius, topLeftRadius, topRightRadius, topRightRadius,
                    bottomRightRadius, bottomRightRadius, bottomLeftRadius, bottomLeftRadius
                )
            }
            drawable.shape = if (isRounded) GradientDrawable.OVAL else GradientDrawable.RECTANGLE
            drawable.cornerRadii = radii
            drawable.setStroke(strokeWidth, strokeColor, dashWidth, dashGap)
            drawable.setColor(attrs.contentColor)

            if (gradientType != GradientType.NONE) {
                drawable.gradientType = gradientType
                drawable.orientation = gradientOrientation
                drawable.gradientRadius = gradientRadius
                drawable.setGradientCenter(gradientCenterX, gradientCenterY)

                val colors = mutableListOf<Int>()
                if (startColor != 0) {
                    (0 until startColorWeight).forEach { _ -> colors.add(startColor) }
                }
                if (centerColor != 0) {
                    (0 until centerColorWeight).forEach { _ -> colors.add(centerColor) }
                }
                if (endColor != 0) {
                    (0 until endColorWeight).forEach { _ -> colors.add(endColor) }
                }
                if (colors.isNotEmpty()) drawable.colors = colors.toIntArray()
            }
        }
    }

    private fun getGradientOrientation(orientation: Int): GradientDrawable.Orientation {
        return when (orientation) {
            0 -> GradientDrawable.Orientation.TOP_BOTTOM
            1 -> GradientDrawable.Orientation.TR_BL
            2 -> GradientDrawable.Orientation.RIGHT_LEFT
            3 -> GradientDrawable.Orientation.BR_TL
            4 -> GradientDrawable.Orientation.BOTTOM_TOP
            5 -> GradientDrawable.Orientation.BL_TR
            6 -> GradientDrawable.Orientation.LEFT_RIGHT
            else -> GradientDrawable.Orientation.TL_BR
        }
    }
}